using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace Microsoft.WowAddonStudio.Lua.RuntimeService.WowBench
{
	/// <summary>
	/// Represents a writer that can write a sequential series of character
	/// into the inner TextBox.
	/// </summary>
	public sealed class WoWBenchOutputWriter : TextWriter, IOutputWriter, IEventProvider
	{
		private enum WriterType
		{
			Normal,
			Information,
			Error
		}

		private readonly IOutputControl textBox;

		/// <summary>
		/// Initializes a new instance of the <see cref="WoWBenchOutputWriter"/> class.
		/// </summary>
		/// <param name="textBox"></param>
		public WoWBenchOutputWriter(IOutputControl textBox)
		{
			this.textBox = textBox;
		}

		#region IOutputWriter Members

		#region Events

		private event EventHandler<OnBeforeWriteEventArgs> onBeforeWrite;
		private event EventHandler<OutputWriterEventArgs> onAfterWrite;

		/// <summary>
		/// Fires before text is sended to output.
		/// </summary>
		public event EventHandler<OnBeforeWriteEventArgs> OnBeforeWrite
		{
			add { onBeforeWrite += value; }
			remove { onBeforeWrite -= value; }
		}

		/// <summary>
		/// Fires after text is sended to output.
		/// </summary>
		public event EventHandler<OutputWriterEventArgs> OnAfterWrite
		{
			add { onAfterWrite += value; }
			remove { onAfterWrite -= value; }
		}

		#endregion


		/// <summary>
		/// Gets the current Encoding.
		/// </summary>
		public override Encoding Encoding
		{
			get { return Encoding.Default; }
		}

		/// <summary>
		/// Appends the text to the current text of a control.
		/// </summary>
		/// <param name="value">The text to append to the current contents of the text box.</param>
		public override void Write(string value)
		{
			if (!string.IsNullOrEmpty(value))
				InvokeAppendText(value.Replace("\n", Environment.NewLine), WriterType.Normal);
		}

		/// <summary>
		/// Appends the text to the current text of a control in new line.
		/// </summary>
		/// <param name="value">The text to append to the current contents of the text box.</param>
		public override void WriteLine(string value)
		{
			Write(value);
			InvokeAppendText(Environment.NewLine, WriterType.Normal);
		}

		/// <summary>
		/// Appends the text to the current text of a control with formatting.
		/// </summary>
		/// <param name="value">The text to append to the current contents of the text box.</param>
		public void WriteFormat(string value)
		{
			Write(Format(value));
		}

		/// <summary>
		/// Appends the text to the current text of a control in new line with formatting.
		/// </summary>
		/// <param name="value">The text to append to the current contents of the text box.</param>
		public void WriteLineFormat(string value)
		{
			WriteLine(Format(value));
		}

		/// <summary>
		/// Appends error text to the current text of a control.
		/// </summary>
		/// <param name="value">The text to append to the current contents of the text box.</param>
		public void WriteErrorText(string value)
		{
			if (!string.IsNullOrEmpty(value))
				InvokeAppendText(Format(value).Replace("\n", Environment.NewLine), WriterType.Error);
		}

		/// <summary>
		/// Appends information text to the current text of a control.
		/// </summary>
		/// <param name="value">The text to append to the current contents of the text box.</param>
		public void WriteInfoText(string value)
		{
			if (!string.IsNullOrEmpty(value))
				InvokeAppendText(Format(value).Replace("\n", Environment.NewLine), WriterType.Information);
		}

		#endregion

		#region WoWBenchOutputWriter Members

		/// <summary>
		/// Asynchronously append the text to the current text of a control .
		/// </summary>
		/// <param name="text"></param>
		/// <param name="writerType"></param>
		private void InvokeAppendText(string text, WriterType writerType)
		{
			var arg = new OnBeforeWriteEventArgs(text /*, textBox.Position*/);

			if (onBeforeWrite != null)
				onBeforeWrite(this, arg);

			if (!arg.Cancel)
			{
				int position;
				switch (writerType)
				{
					case WriterType.Error:
						{
							position = textBox.AppendErrorText(text);
							break;
						}
					case WriterType.Information:
						{
							position = textBox.AppendInfoText(text);
							break;
						}
					default:
						{
							position = textBox.AppendText(text);
							break;
						}
				}

				if (onAfterWrite != null)
					onAfterWrite(this, new OutputWriterEventArgs(text, position));
			}
		}

		/// <summary>
		/// Asynchronously executes the delegate on the thread that created this object.
		/// </summary>
		/// <param name="invoker"></param>
		/// <param name="text"></param>
		private void InvokeWrite(MethodInvoker invoker, string text)
		{
			var arg = new OnBeforeWriteEventArgs(text);

			if (onBeforeWrite != null)
				onBeforeWrite(this, arg);

			if (!arg.Cancel)
			{
				if (textBox.InvokeRequired)
				{
					textBox.BeginInvoke(invoker);
				}
				else
				{
					invoker.Invoke();
				}

				if (onAfterWrite != null)
					onAfterWrite(this, new OutputWriterEventArgs(text));
			}
		}

		/// <summary>
		/// Formats the input string.
		/// </summary>
		/// <param name="value"></param>
		/// <returns></returns>
		private static string Format(string value)
		{
			string result = value;
			if (!string.IsNullOrEmpty(value))
			{
				result = result.Replace("\r\n", "\n");
				result = result.Replace("\r", "");
			}
			return result;
		}

		/// <summary>
		/// Detach all listeners from events.
		/// </summary>
		public void UnsubscribeEvents()
		{
			onBeforeWrite = null;
			onAfterWrite = null;
		}

		#endregion

		#region IDisposable Members

		/// <summary>
		/// Releases the unmanaged resources used by the <see cref="T:System.IO.TextWriter"/> 
		/// and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing">true to release both managed and unmanaged resources;
		/// false to release only unmanaged resources.</param>
		protected override void Dispose(bool disposing)
		{
			try
			{
				UnsubscribeEvents();
			}
			finally
			{
				base.Dispose(disposing);
			}
		}

		#endregion

	}
}